using System;
using System.Collections.Generic;
using System.Net;
using System.Text;
using System.Threading;

namespace YouTubeDupe
{
    /// <summary>
    /// The DownloadServer module of the YouTube Duplicate Video Finder.
    /// </summary>
    public class DownloadServer
    {
        SessionController _controller;

        string _pageSource;

        HttpListener _pageServer;
        HttpListener _functionServer;

        Thread _backgroundThread;
        Thread _functionServerThread;
        Thread _pageServerThread;

        int _videosDownloaded = 0;

        /// <summary>
        /// Instantiates an instance of the download server module.
        /// </summary>
        /// <param name="controller">The controller for module.</param>
        public DownloadServer(SessionController controller)
        {
            if (controller == null)
            {
                throw new NullReferenceException("Controller can't be null.");
            }

            _controller = controller;

            // Check if the connection exists.
            if (!YouTubeAPI.ConnectionExists)
            {
                //
                // Implement a prettier error message.
                //
                _pageSource = "YouTube API can't be reached.";
            }
            else
            {
                // If a connection exists, configure the function server and generate the page source.
                _pageSource = Resources.DownloadServerPageSource;

                // Configure the function server.
                _functionServer = new HttpListener();
                _functionServer.Prefixes.Add("http://localhost/" + _controller.SessionID + "/downloadServer/functionServer/");
                _functionServerThread = new Thread(new ThreadStart(functionServerProcess));

                // Configure the background thread.
                _backgroundThread = new Thread(new ThreadStart(backgroundProcess));
            }

            // Configure the page server.
            _pageServer = new HttpListener();
            _pageServer.Prefixes.Add("http://localhost/" + _controller.SessionID + "/downloadServer/pageServer/");
            _pageServerThread = new Thread(new ThreadStart(pageServerProcess));
        }

        /// <summary>
        /// Starts the module and accept incoming requests.
        /// </summary>
        public void Start()
        {
            // Start the http listeners.
            _functionServer.Start();
            _pageServer.Start();

            // Start the http listener threads.
            _backgroundThread.Start();
            _functionServerThread.Start();
            _pageServerThread.Start();
        }

        /// <summary>
        /// Stops the module and rejects all incoming requests.
        /// </summary>
        public void Stop()
        {
            // Stop the http listeners.
            _pageServer.Abort();
            _functionServer.Abort();

            // Stop all threads.
            _backgroundThread.Abort();
            _pageServerThread.Abort();
            _functionServerThread.Abort();
        }

        /// <summary>
        /// The function server process. Accepts incoming http requests and sends appropriate responses.
        /// </summary>
        private void functionServerProcess()
        {
            while (true)
            {
                try
                {
                    // Get an incoming request.
                    HttpListenerContext context = _functionServer.GetContext();

                    // Get the function name of the request.
                    string functionName = context.Request.QueryString["function"];

                    // Return if no function name is specified.
                    if (functionName == null) return;

                    context.Response.AppendHeader("Cache-Control", "no-cache");
                    bool changeModule = false;

                    switch (functionName)
                    {
                        case "getDownloadCount":
                            {
                                // Get the current download count.
                                using (System.IO.StreamWriter writer = new System.IO.StreamWriter(context.Response.OutputStream))
                                {
                                    if (_videosDownloaded == _controller.UserSpecifiedVideoCount)
                                    {
                                        // If the videos downloaded equals the user specified count, write an operation complete response.
                                        writer.Write("All videos downloaded.");
                                        changeModule = true;
                                    }
                                    else
                                    {
                                        // Else write the number of videos downloaded.
                                        writer.Write(_videosDownloaded + " of " + _controller.UserSpecifiedVideoCount + " videos downloaded.");
                                    }
                                    writer.Close();
                                }
                                break;
                            }
                        case "moveNext":
                            {
                                // Update the # of downloaded videos and move to the next module.
                                OnPropertyChanged(new PropertyChangedEventArgs("UserSpecifiedVideoCount", _videosDownloaded));
                                changeModule = true;
                                break;
                            }

                    }

                    // Close the response.
                    context.Response.Close();

                    if (changeModule)
                    {
                        OnModuleChanging(EventArgs.Empty);
                    }
                }
                // These catchers are needed due to the fact that the GetContext method throws these exceptions
                // when the thread its executing on is aborted.
                catch (HttpListenerException) { }
                catch (ObjectDisposedException) { }
            }
        }

        /// <summary>
        /// The page server process. Accepts incoming http requests and sends the html page back.
        /// </summary>
        private void pageServerProcess()
        {
            while (true)
            {
                try
                {
                    HttpListenerContext context = _pageServer.GetContext();
                    context.Response.AppendHeader("Cache-Control", "no-cache");
                    using (System.IO.StreamWriter writer = new System.IO.StreamWriter(context.Response.OutputStream))
                    {
                        writer.Write(_pageSource);
                        writer.Close();
                    }
                    context.Response.Close();
                }
                // These catchers are needed due to the fact that the GetContext method throws these exceptions
                // when the thread its executing on is aborted.
                catch (HttpListenerException) { }
                catch (ObjectDisposedException) { }
            }
        }

        /// <summary>
        /// The background process. Downloads all the video information using the YouTube API.
        /// </summary>
        private void backgroundProcess()
        {
            // Declare the current page variable.
            int currentPage = 1;

            bool inVideo = false;
            string id = null;
            string title = null;
            string author = null;
            string url = null;
            string length = null;
            string description = null;
            string tags = null;
            string viewCount = null;
            string thumbnailUrl = null;

            try
            {
                while (_videosDownloaded < _controller.UserSpecifiedVideoCount)
                {
                    // Get the current page of videos from the YouTube API.
                    string pageXml = YouTubeAPI.GetPageOfVideos(_controller.SearchTerm, currentPage++, 100, _controller.DeveloperID);

                    // Create a stream from the xml page.
                    using (System.IO.StringReader stringReader = new System.IO.StringReader(pageXml))
                    {
                        // Create an xml reader from the stream.
                        using (System.Xml.XmlReader reader = System.Xml.XmlReader.Create(stringReader))
                        {
                            // Iterate through the entire stream while there are still videos to download.
                            while (reader.Read() && _videosDownloaded < _controller.UserSpecifiedVideoCount)
                            {
                                // All code below parses the returned xml for videos.
                                
                                if (reader.Name.Equals("video") && reader.NodeType == System.Xml.XmlNodeType.Element)
                                {
                                    // A video element was entered.
                                    inVideo = true;
                                }
                                else if (reader.Name.Equals("video") && reader.NodeType == System.Xml.XmlNodeType.EndElement)
                                {
                                    // A video element was exited.
                                    inVideo = false;

                                    try
                                    {
                                        Video newVideo = new Video(id, title, author, url, length, description,
                                            tags, viewCount, thumbnailUrl);

                                        // Add the new video.
                                        lock (_controller.VideoQueue)
                                        {
                                            _controller.VideoQueue.Enqueue(newVideo);
                                        }

                                        // Increase the download counter.
                                        _videosDownloaded++;
                                    }
                                    // video failed to downloaded thumbnail
                                    catch (WebException) { }
                                }
                                else if (inVideo && reader.Name.Equals("id") && reader.NodeType == System.Xml.XmlNodeType.Element)
                                {
                                    // The video id was read.
                                    reader.Read();
                                    id = reader.Value;
                                }
                                else if (inVideo && reader.Name.Equals("title") && reader.NodeType == System.Xml.XmlNodeType.Element)
                                {
                                    // The video title was read.
                                    reader.Read();
                                    title = reader.Value;
                                }
                                else if (inVideo && reader.Name.Equals("author") && reader.NodeType == System.Xml.XmlNodeType.Element)
                                {
                                    // The video author was read.
                                    reader.Read();
                                    author = reader.Value;
                                }
                                else if (inVideo && reader.Name.Equals("url") && reader.NodeType == System.Xml.XmlNodeType.Element)
                                {
                                    // The video url was read.
                                    reader.Read();
                                    url = reader.Value;
                                }
                                else if (inVideo && reader.Name.Equals("length_seconds") && reader.NodeType == System.Xml.XmlNodeType.Element)
                                {
                                    // The video length was read.
                                    reader.Read();
                                    length = reader.Value;
                                }
                                else if (inVideo && reader.Name.Equals("description") && reader.NodeType == System.Xml.XmlNodeType.Element)
                                {
                                    // The video description was read.
                                    reader.Read();
                                    description = reader.Value;
                                }
                                else if (inVideo && reader.Name.Equals("tags") && reader.NodeType == System.Xml.XmlNodeType.Element)
                                {
                                    // The videos tags were read.
                                    reader.Read();
                                    tags = reader.Value;
                                }
                                else if (inVideo && reader.Name.Equals("view_count") && reader.NodeType == System.Xml.XmlNodeType.Element)
                                {
                                    // The video view count was read.
                                    reader.Read();
                                    viewCount = reader.Value;
                                }
                                else if (inVideo && reader.Name.Equals("thumbnail_url") && reader.NodeType == System.Xml.XmlNodeType.Element)
                                {
                                    // The video thumbnail was read.
                                    reader.Read();
                                    thumbnailUrl = reader.Value;
                                }
                            }
                            reader.Close();
                        }
                        stringReader.Close();
                    }
                }
            }
            // The following exception handles thread abortion.
            catch (ThreadAbortException) { }
        }

        /// <summary>
        /// Triggered when a property needs to change in the controller.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// Handes the PropertyChanged event.
        /// </summary>
        private void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, e);
            }
        }

        /// <summary>
        /// Triggered when module control is changing.
        /// </summary>
        public event EventHandler ModuleChanging;

        /// <summary>
        /// Handles the ModuleChanging event.
        /// </summary>
        private void OnModuleChanging(EventArgs e)
        {
            if (ModuleChanging != null)
            {
                ModuleChanging(this, e);
            }
        }

        /// <summary>
        /// Gets the # of videos downloaded.
        /// </summary>
        public int VideosDownloaded
        {
            get { return _videosDownloaded; }
        }
    }
}
